Current Weather Quicklooks#

Code to Create Plots#

Hide code cell source
import sage_data_client
from bokeh.models.formatters import DatetimeTickFormatter
import hvplot.pandas
import hvplot.xarray
import holoviews as hv
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import xarray as xr
import matplotlib.pyplot as plt
from metpy.plots import USCOUNTIES
import metpy.calc as mpcalc
import act
import numpy as np
import pandas as pd
import warnings
from bokeh.models import DatetimeTickFormatter
import panel as pn

start = "-6h"

def apply_formatter(plot, element):
    plot.handles['xaxis'].formatter = DatetimeTickFormatter(hours='%m/%d/%Y \n %H:%M',
                                                            minutes='%m/%d/%Y \n %H:%M',
                                                            hourmin='%m/%d/%Y \n %H:%M',
                                                            days='%m/%d/%Y \n %H:%M',
                                                            months='%m/%d/%Y \n %H:%M')
xr.set_options(keep_attrs=True)
warnings.filterwarnings("ignore")
hv.extension("bokeh")



# Dictionary for renaming to standard names
variable_rename_dict = {'wxt.env.humidity':'relative_humidity',
                        'wxt.env.pressure':'air_pressure',
                        'wxt.env.temp':'air_temperature',
                        'wxt.heater.temp':'heater_temperature',
                        'wxt.heater.volt':'heater_voltage',
                        'wxt.rain.accumulation':'rain_accumulation',
                        'wxt.wind.direction':'wind_direction',
                        'wxt.wind.speed':'wind_speed',
                        'sys.gps.lat':'latitude',
                        'sys.gps.lon':'longitude',
                    }

# Dictionary for units that are missing
units_dict = {'wxt.env.temp': 'degC',
              'wxt.env.pressure':'hPa',
              'wxt.env.humidity':'percent',
              'wxt.wind.speed':'m/s',
              'wxt.wind.direction':'degrees'}

def generate_data_array(df, variable, rename_variable_dict=variable_rename_dict):
    new_variable_name = rename_variable_dict[variable]
    df_variable= df.loc[df.name == variable]
    ds = df_variable.to_xarray().rename({'value':new_variable_name,
                                         'timestamp':'time',
                                         'meta.vsn':'node'})
    ds[new_variable_name].attrs['units'] = df_variable['meta.units'].values[0]
    ds['time'] = pd.to_datetime(ds.time)
    ds.attrs['datastream'] = ds.node.values[0]
    return ds[[new_variable_name]]

def generate_dataset(df, variables, rename_variable_dict=variable_rename_dict):
    try:
        reindexed = df.set_index(['meta.vsn', 'timestamp'])
    except:
        reindexed = df.set_index(['timestamp'])
    return xr.merge([generate_data_array(reindexed, variable) for variable in variables])


# Query and load for n numbder of days
wxt_df = sage_data_client.query(
    start=start,
    filter={
        "sensor": "vaisala-wxt536",
        "name": "wxt.env.*"
    }
)

wxt_df1 = sage_data_client.query(
    start=start,
    filter={
        "sensor": "vaisala-wxt536",
        "name": "wxt.wind.*"
    }
)

wxt_df = pd.concat([wxt_df, wxt_df1])

try:

    # Discover what variables we have and what to load into xarray
    wxt_variables = wxt_df.name.unique()
    wxt_df['meta.units'] = wxt_df.name.map(units_dict)
    wxt_ds = generate_dataset(wxt_df, wxt_variables).squeeze().metpy.parse_cf()
    wxt_ds['air_dewpoint_temperature'] = mpcalc.dewpoint_from_relative_humidity(wxt_ds.air_temperature, wxt_ds.relative_humidity)

    # Resample to 1 minute freqency
    minute_ds = wxt_ds.resample(time='1T').mean()

    plots = []
    temp_plot = wxt_ds.air_temperature.hvplot(color='red',
                                              label='Air Temperature (degC)')
    dewp_plot = wxt_ds.air_dewpoint_temperature.hvplot(color='green',
                                                       label='Dewpoint Temperature (degC)')
    plots.append((temp_plot * dewp_plot).opts(hooks=[apply_formatter]))
    
    meteogram_variables = ['wind_speed', 'wind_direction']
    for variable in meteogram_variables:
        plots.append((wxt_ds[variable].hvplot.line(label='10 Hz Data') * 
                     minute_ds[variable].hvplot.line(label='1 Minute Data')).opts(hooks=[apply_formatter]))
    
    
except:
    plots = pn.Row(title='No Data Available')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[1], line 103
    102 plots = []
--> 103 temp_plot = wxt_ds.air_temperature.hvplot(color='red',
    104                                           label='Air Temperature (degC)')
    105 dewp_plot = wxt_ds.air_dewpoint_temperature.hvplot(color='green',
    106                                                    label='Dewpoint Temperature (degC)')

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/hvplot/plotting/core.py:95, in hvPlotBase.__call__(self, x, y, kind, **kwds)
     93         return pn.panel(plot, **panel_dict)
---> 95 return self._get_converter(x, y, kind, **kwds)(kind, x, y)

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/hvplot/converter.py:1275, in HoloViewsConverter.__call__(self, kind, x, y)
   1273     dataset = dataset.redim(**self._redim)
-> 1275 obj = method(x, y)
   1276 obj._dataset = dataset

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/hvplot/converter.py:2184, in HoloViewsConverter.image(self, x, y, z, data)
   2183 if self.geo: params['crs'] = self.crs
-> 2184 return (element(data, [x, y], z, **params).redim(**redim)
   2185         .apply(self._set_backends_opts, cur_opts=cur_opts, compat_opts=compat_opts))

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/holoviews/element/raster.py:291, in Image.__init__(self, data, kdims, vdims, bounds, extents, xdensity, ydensity, rtol, **params)
    290 yvals = self.dimension_values(1, False)
--> 291 b, t, ydensity, _ = util.bound_range(yvals, ydensity, self._time_unit)
    292 bounds = BoundingBox(points=((l, b), (r, t)))

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/holoviews/core/util.py:2084, in bound_range(vals, density, time_unit)
   2083 warnings.filterwarnings('ignore', r'invalid value encountered in (double_scalars|scalar divide)')
-> 2084 full_precision_density = compute_density(low, high, len(vals)-1)
   2085 with np.errstate(over='ignore'):

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/holoviews/core/util.py:2115, in compute_density(start, end, length, time_unit)
   2114 if isinstance(end, int): end = float(end)
-> 2115 diff = end-start
   2116 if isinstance(diff, timedelta_types):

TypeError: unsupported operand type(s) for -: 'str' and 'str'

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
Cell In[1], line 116
    111         plots.append((wxt_ds[variable].hvplot.line(label='10 Hz Data') * 
    112                      minute_ds[variable].hvplot.line(label='1 Minute Data')).opts(hooks=[apply_formatter]))
    115 except:
--> 116     plots = pn.Row(title='No Data Available')

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/panel/layout/base.py:808, in ListPanel.__init__(self, *objects, **params)
    806     if not resolve_ref(objects) or iscoroutinefunction(objects):
    807         params['objects'] = [panel(pane) for pane in objects]
--> 808 super(Panel, self).__init__(**params)

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/panel/reactive.py:556, in Reactive.__init__(self, refs, **params)
    554     if refs:
    555         param.bind(self._sync_refs, *refs, watch=True)
--> 556 super().__init__(**params)

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/panel/reactive.py:118, in Syncable.__init__(self, **params)
    116 def __init__(self, **params):
    117     self._themer = None
--> 118     super().__init__(**params)
    120     # Useful when updating model properties which trigger potentially
    121     # recursive events
    122     self._updating = False

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/panel/viewable.py:694, in Viewable.__init__(self, **params)
    692 def __init__(self, **params):
    693     hooks = params.pop('hooks', [])
--> 694     super().__init__(**params)
    695     self._hooks = hooks
    697     self._update_loading()

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/panel/viewable.py:532, in Renderable.__init__(self, **params)
    530 self._comms = {}
    531 self._kernels = {}
--> 532 super().__init__(**params)
    533 self._found_links = set()
    534 self._logger = logging.getLogger(f'{__name__}.{type(self).__name__}')

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/panel/viewable.py:298, in Layoutable.__init__(self, **params)
    296 if 'design' not in params and self.param.design.default is None:
    297     params['design'] = config.design
--> 298 super().__init__(**params)

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/param/parameterized.py:4148, in Parameterized.__init__(self, **params)
   4146 if self.param.name.default == self.__class__.__name__:
   4147     self.param._generate_name()
-> 4148 refs, deps = self.param._setup_params(**params)
   4149 object_count += 1
   4151 self._param__private.initialized = True

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/param/parameterized.py:1678, in as_uninitialized.<locals>.override_initialization(self_, *args, **kw)
   1676 original_initialized = parameterized_instance._param__private.initialized
   1677 parameterized_instance._param__private.initialized = False
-> 1678 ret = fn(self_, *args, **kw)
   1679 parameterized_instance._param__private.initialized = original_initialized
   1680 return ret

File /usr/share/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/param/parameterized.py:1934, in Parameters._setup_params(self_, **params)
   1932 desc = self_.cls.get_param_descriptor(name)[0] # pylint: disable-msg=E1101
   1933 if not desc:
-> 1934     raise TypeError(
   1935         f"{self.__class__.__name__}.__init__() got an unexpected "
   1936         f"keyword argument {name!r}"
   1937     )
   1939 pobj = objects.get(name)
   1940 if pobj is None or not pobj.allow_refs:
   1941     # Until Parameter.allow_refs=True by default we have to
   1942     # speculatively evaluate a values to check whether they
   1943     # contain a reference and warn the user that the
   1944     # behavior may change in future.

TypeError: Row.__init__() got an unexpected keyword argument 'title'

Meteogram with Temperature, Dewpoint, and Winds#

This is what we call a meteogram, which is a timeseries of:

  • Temperature and Dewpoint (same plot)

  • Wind speed and direction (different plots)

The data is collected at 10 Hertz (10 observations per second) and averaged to 1 Hertz (1 per second) for the wind plots to illustrate turbulence!

Hide code cell source
if len(plots) > 0:
    display(hv.Layout(plots).cols(1))
else:
    print('Issue with wxt datastream')

Wind Rose Plot#

This plot shows the freqency and direction of the winds, separated by the magnitude of the winds. This helps with identifying where the wind was coming from and how strong that wind was.

Hide code cell source
if minute_ds is not None:
    WindDisplay = act.plotting.WindRoseDisplay(minute_ds, figsize=(6, 8), subplot_shape=(1,))
    WindDisplay.plot(
        'wind_direction', 'wind_speed', spd_bins=np.linspace(0, 20, 5), num_dirs=30, tick_interval=2, subplot_index=(0,)
    )
    plt.show()
else:
    print('Issue with wxt datastream')
../../_images/c5d4783a7e7937f2fbb3246a8a88418fcfb818e37a3718f7cd7472a8c427eed9.png